/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.sd.chat.web;

import com.google.gson.Gson;
import com.sd.chat.ChatClientView;
import com.sd.chat.ClientManager;
import com.sd.chat.Logger;
import com.sd.chat.Message;
import com.sd.chat.ServerData;
import com.sd.chat.UserList;
import com.sd.chat.Utilizador;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import org.apache.catalina.websocket.WebSocketServlet;
import org.apache.catalina.websocket.MessageInbound;
import org.apache.catalina.websocket.StreamInbound;
import org.apache.catalina.websocket.WsOutbound;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.FileChannel;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.GregorianCalendar;
import java.util.Set;
import java.util.Vector;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicInteger;
import javax.servlet.http.HttpServletRequest;

/**
 *
 * @author Old
 */
public class HTTPBridge extends WebSocketServlet {

    private final AtomicInteger sequence = new AtomicInteger(0);
    private final Set<ChatMessageInbound> connections = new CopyOnWriteArraySet<ChatMessageInbound>();
    private final ArrayList<ChatChannel> chatChannels = new ArrayList<ChatChannel>();

    @Override
    protected StreamInbound createWebSocketInbound(String subProtocol, HttpServletRequest request) {
        return new ChatMessageInbound(sequence.incrementAndGet());
    }

    public final class ChatMessageInbound extends MessageInbound implements ChatClientView {

        public String nickname;
        private ClientManager manager;
        private Logger log = new Logger(true);

        private ChatMessageInbound(int id) {
            this.nickname = "Client" + id;
            manager = new ClientManager(new ServerData(), this);
        }

        @Override
        protected void onOpen(WsOutbound outbound) {
            connections.add(this);
            manager.start();
            manager.setConnectionInfo(WebConfigs.mainServerAdress, WebConfigs.backupServerAdress, true);
        }

        @Override
        protected void onClose(int status) {
            for (int c = 0; c < chatChannels.size(); c++) {
                if (chatChannels.get(c).connections.contains(this)) {
                    chatChannels.get(c).connections.remove(this);
                    log.outln("HTTPBridge removed user from chat channel");
                    if (chatChannels.get(c).connections.isEmpty()) {
                        chatChannels.remove(c);
                        c--;
                        log.outln("HTTPBridge chat channel is empty and got destroyed");
                        //send message to all with channels
                    }
                }

            }
            connections.remove(this);
            manager.shutdown();
            sendMsgWithChannels();
        }

        @Override
        protected void onTextMessage(CharBuffer message) throws IOException {
            // never trust the client
            if (message.length() > 3) {

                message.rewind(); //ir para o inicio

                //3 chars para sabermos o tipo de mensagem
                char c1 = message.get();
                char c2 = message.get();
                char c3 = message.get();

                //os dados da mensagem em si
                char data[] = new char[message.length()];
                message.get(data, 0, message.length());


                Gson gson = new Gson();
                //switch do código de mensagem
                log.outln("HTTPBridge message received: "+new String(data));
                if (c1 == '1') { //Login messages
                    Userinfo userData = gson.fromJson(new String(data), Userinfo.class);
                    if (c2 == '1') { //login
                        manager.setAuthInfo(userData.getUsername(), userData.getPassword(), false);
                    } else if (c2 == '2') { //register
                        manager.setAuthInfo(userData.getUsername(), userData.getPassword(), true);
                    }
                } else if (c1 == '2') { //Posts
                    Postinfo postData = gson.fromJson(new String(data), Postinfo.class);

                    if (c2 == '1') { //newPosts
                        if (!postData.isComment()) {
                            boolean isNow = postData.isIsNow();

                            SimpleDateFormat dateFormat = new SimpleDateFormat("dd-MM-yyyy H:mm:ss");
                            try {
                                GregorianCalendar pDate = new GregorianCalendar();
                                if (!isNow) {
                                    pDate.setTime(dateFormat.parse(postData.getDate()));
                                }

                                boolean hasPic = postData.isHasPic();
                                String picLoc = "";
                                if (hasPic) {
                                    picLoc = postData.getPicLoc();
                                }
                                manager.newPost(postData.getText(), hasPic, picLoc, pDate);

                            } catch (ParseException ex) {
                                log.erro("HTTPBridge Data de post inválida.");
                            }
                        } else {
                            manager.newCommentSubmit(postData.getText(), postData.getPostId());
                        }
                    } else if (c2 == '2') {//edits

                        manager.editSubmit(filter(postData.getText()), postData.getId());

                    } else if (c2 == '3') {//eliminates

                        if (postData.isComment()) {
                            manager.eliminateSubmit(postData.getPostId(), false);
                        } else {
                            manager.eliminateSubmit(postData.getId(), true);
                        }

                    }

                } else if (c1 == '3') { //PMs

                    PMinfo pmData = gson.fromJson(new String(data), PMinfo.class);
                    Vector<Integer> tos = new Vector<Integer>();
                    for (int i = 0; i < pmData.getTo().length; i++) {
                        tos.add(pmData.getTo()[i]);
                    }
                    Message mess = new Message(manager.getUserInfo(), tos, pmData.getText());
                    manager.newMessageSubmit(mess);

                } else if (c1 == '4') { //Channel Stuff
                    ChatMessage chatData = gson.fromJson(new String(data), ChatMessage.class);

                    if (c2 == '1') {//enter channel
                        for (int c = 0; c < chatChannels.size(); c++) {
                            if (chatChannels.get(c).name.equals(chatData.getName())) {
                                if(!chatChannels.get(c).connections.contains(this)){
                                    chatChannels.get(c).connections.add(this);
                                }
                                //send message to all with channels
                                sendMsgWithChannels();
                                break;
                            }
                        }
                    } else if (c2 == '2') {//create channel
                        boolean channelExists = false;
                        for (int c = 0; c < chatChannels.size(); c++) {
                            if (chatChannels.get(c).name.equals(chatData.getName())) {
                                if(!chatChannels.get(c).connections.contains(this)){
                                    chatChannels.get(c).connections.add(this);
                                }
                                log.outln("HTTPBridge chat channel existed added user");
                                channelExists = true;
                            }
                        }
                        if (!channelExists) {
                            chatChannels.add(new ChatChannel(chatData.getName()));
                            chatChannels.get(chatChannels.size() - 1).connections.add(this);
                            log.outln("HTTPBridge created chat channel");
                        }
                        sendMsgWithChannels();
                        //send message to all with channels
                    } else if (c2 == '3') {//leave channel
                        for (int c = 0; c < chatChannels.size(); c++) {
                            if (chatChannels.get(c).name.equals(chatData.getName())) {
                                chatChannels.get(c).connections.remove(this);
                                log.outln("HTTPBridge removed user from chat channel");
                                if (chatChannels.get(c).connections.isEmpty()) {
                                    chatChannels.remove(c);
                                    log.outln("HTTPBridge chat channel is empty and got destroyed");
                                    //send message to all with channels
                                }
                                sendMsgWithChannels();
                                break;
                            }

                        }

                    } else if (c2 == '4') {//send message
                        for (int c = 0; c < chatChannels.size(); c++) {
                            if (chatChannels.get(c).name.equals(chatData.getName())) {
                                for (ChatMessageInbound connection : chatChannels.get(c).connections) {
                                    try {
                                        ChatMessage cmsg = new ChatMessage();
                                        cmsg.setName(chatData.getName());
                                        cmsg.setData(this.nickname + ": " + filter(chatData.getData()));
                                        CharBuffer buffer = CharBuffer.wrap("42" + gson.toJson(cmsg));
                                        log.outln("HTTPBridge chat sending message to user, msg: " + "42" + gson.toJson(cmsg));
                                        connection.getWsOutbound().writeTextMessage(buffer);
                                    } catch (IOException ignore) {
                                    }
                                }
                                break;
                            }

                        }
                    }
                }
            }
        }

        private void sendMsgWithChannelsToOne(ChatMessageInbound con) {
            try {
                CharBuffer buffer = CharBuffer.wrap("41" + getMsgWithChannels());
                con.getWsOutbound().writeTextMessage(buffer);
            } catch (IOException ignore) {
            }
        }

        private void sendMsgWithChannels() {
            String msg = getMsgWithChannels();
            for (ChatMessageInbound connection : connections) {
                try {
                    CharBuffer buffer = CharBuffer.wrap("41" + msg);
                    connection.getWsOutbound().writeTextMessage(buffer);
                } catch (IOException ignore) {
                }
            }
        }

        private String getMsgWithChannels() {
            String channelList = "[";
            for (int c = 0; c < chatChannels.size(); c++) {
                if (c > 0) {
                    channelList += ",";
                }
                channelList += "{";
                channelList += "\"name\":\"" + chatChannels.get(c).name + "\",";
                channelList += "\"users\":[";
                int u = 0;
                for (ChatMessageInbound connection : chatChannels.get(c).connections) {
                    if (u > 0) {
                        channelList += ",";
                    }
                    channelList += "\"" + connection.nickname + "\"";
                    u++;
                }
                channelList += "]";
                channelList += "}";
            }
            channelList += "]";
            return channelList;
        }

        private void sendMessage(String message) {
            try {
                CharBuffer buffer = CharBuffer.wrap(message);
                this.getWsOutbound().writeTextMessage(buffer);
            } catch (IOException ignore) {
                log.erro("HTTPBridge envio de mensagem websocket");
            }
        }

        private void broadcast(String message) { // send message to all
            for (ChatMessageInbound connection : connections) {
                try {
                    CharBuffer buffer = CharBuffer.wrap(message);
                    connection.getWsOutbound().writeTextMessage(buffer);
                } catch (IOException ignore) {
                }
            }
        }

        public String filter(String message) {
            if (message == null) {
                return (null);
            }
            // filter characters that are sensitive in HTML
            char content[] = new char[message.length()];
            message.getChars(0, message.length(), content, 0);
            StringBuilder result = new StringBuilder(content.length + 50);
            for (int i = 0; i < content.length; i++) {
                switch (content[i]) {
                    case '<':
                        result.append("&lt;");
                        break;
                    case '>':
                        result.append("&gt;");
                        break;
                    case '&':
                        result.append("&amp;");
                        break;
                    case '"':
                        result.append("&quot;");
                        break;
                    default:
                        result.append(content[i]);
                }
            }
            return (result.toString());
        }

        @Override
        protected void onBinaryMessage(ByteBuffer message) throws IOException {
            throw new UnsupportedOperationException("Binary messages not supported.");
        }

        @Override
        public void displayMsg(String msg, int color) {
        }

        @Override
        public void setDisconnected() {
            Gson gson = new Gson();
            String[] strs = new String[1];
            strs[0] = " ";
            sendMessage("11" + gson.toJson(strs));
        }

        @Override
        public void setConnectionAccepted() {
            Gson gson = new Gson();
            String[] strs = new String[1];
            strs[0] = " ";
            sendMessage("12" + gson.toJson(strs));
        }

        @Override
        public void setAuthFailMessage(String msg) {
            Gson gson = new Gson();
            String[] strs = new String[1];
            strs[0] = msg;
            sendMessage("13" + gson.toJson(strs));
        }

        @Override
        public void setAuthenticated() {
            Gson gson = new Gson();
            String[] strs = new String[1];
            strs[0] = manager.getUserInfo().getNome();
            sendMessage("14" + gson.toJson(strs));
            sendMsgWithChannelsToOne(this);
        }

        @Override
        public void updatePosts(ServerData db) {
            Postinfo[] posts = new Postinfo[db.getPosts().size() + db.getComments().size()];
            for (int j = 0; j < db.getPosts().size(); j++) {
                String picLocWeb = "";
                if (db.getPosts().get(j).isHaspic()) {
                    log.outln("HTTPBridge " + db.getPosts().get(j).getPic());
                    File tempPic = new File(db.getPosts().get(j).getPic());
                    File destPix = new File(WebConfigs.webFiles + "\\" + db.getPosts().get(j).getPic());
                    if (!destPix.exists()) {
                        try {
                            copyFile(tempPic, destPix);
                        } catch (IOException ex) {
                            log.erro("HTTPBridge a copiar ficheiro para " + WebConfigs.webFiles + "\\" + db.getPosts().get(j).getPic());
                        }
                    }
                    picLocWeb = "http://localhost:8080/chatsd/" + db.getPosts().get(j).getPic();
                }
                posts[j] = new Postinfo();
                posts[j].setId(db.getPosts().get(j).getId());
                posts[j].setChannel(-1);
                posts[j].setComment(false);
                posts[j].setDate(db.getPosts().get(j).getData().getTime().toString());
                posts[j].setHasPic(db.getPosts().get(j).isHaspic());
                posts[j].setIsNow(true);
                posts[j].setPicLoc(picLocWeb);
                posts[j].setPostId(db.getPosts().get(j).getId());
                posts[j].setText(db.getPosts().get(j).getText());
                posts[j].setUtilizador(db.getPosts().get(j).getUser().getNome());
            }
            for (int j = 0; j < db.getComments().size(); j++) {
                posts[j + db.getPosts().size()] = new Postinfo();
                posts[j + db.getPosts().size()].setId(db.getComments().get(j).getId());
                posts[j + db.getPosts().size()].setChannel(-1);
                posts[j + db.getPosts().size()].setComment(true);
                posts[j + db.getPosts().size()].setDate(db.getComments().get(j).getData().getTime().toString());
                posts[j + db.getPosts().size()].setHasPic(false);
                posts[j + db.getPosts().size()].setIsNow(true);
                posts[j + db.getPosts().size()].setPicLoc("");
                posts[j + db.getPosts().size()].setPostId(db.getComments().get(j).getPostId());
                posts[j + db.getPosts().size()].setText(db.getComments().get(j).getText());
                posts[j + db.getPosts().size()].setUtilizador(db.getComments().get(j).getUser().getNome());
            }
            Gson gson = new Gson();
            sendMessage("22" + gson.toJson(posts));
        }

        @Override
        public void updateUsers(UserList udb) {
            Userinfo[] users = new Userinfo[udb.getUserIds().size()];
            for (int j = 0; j < udb.getUserIds().size(); j++) {
                users[j] = new Userinfo();
                users[j].setUsername(udb.getUserNames().get(j));
                users[j].setPassword(udb.getUserIds().get(j) + "");
            }
            Gson gson = new Gson();
            sendMessage("23" + gson.toJson(users));
        }

        @Override
        public void updatePMs(ServerData db) {
            Gson gson = new Gson();
            String mensagemPMs = "[";

            for (int u = 0; u < db.getPms().size(); u++) {
                if (u > 0) {
                    mensagemPMs += ",";
                }
                int[] tempTos = new int[db.getPms().get(u).getTo().size()];
                for (int t = 0; t < db.getPms().get(u).getTo().size(); t++) {
                    tempTos[t] = db.getPms().get(u).getTo().get(t);
                }
                mensagemPMs += "{";
                mensagemPMs += "\"from\":\"" + db.getPms().get(u).getFrom().getNome() + "\",";
                mensagemPMs += "\"text\":" + gson.toJson(db.getPms().get(u).getText()) + ",";
                mensagemPMs += "\"to\":" + gson.toJson(tempTos) + ",";
                mensagemPMs += "\"data\":" + gson.toJson(db.getPms().get(u).getData().getTime().toString()) + ",";
                mensagemPMs += "}";
            }

            mensagemPMs += "]";

            sendMessage("31" + mensagemPMs);
        }

        @Override
        public void setEsteUser(Utilizador esteUser) {
            Gson gson = new Gson();
            Userinfo userI = new Userinfo();
            this.nickname = esteUser.getNome();
            userI.setUsername(esteUser.getNome());
            userI.setPassword(esteUser.getId() + "");
            sendMessage("15" + gson.toJson(userI));
        }

        @Override
        public Utilizador getEsteUser() {
            return new Utilizador("", "", 0);
        }

        public void copyFile(File sourceFile, File destFile) throws IOException {
            if (!destFile.exists()) {
                destFile.createNewFile();
            }

            FileChannel source = null;
            FileChannel destination = null;

            try {
                source = new FileInputStream(sourceFile).getChannel();
                destination = new FileOutputStream(destFile).getChannel();
                destination.transferFrom(source, 0, source.size());
            } finally {
                if (source != null) {
                    source.close();
                }
                if (destination != null) {
                    destination.close();
                }
            }
        }
    }
}
